aoトークンおよびサブレッジャー仕様
ステータス: DRAFT-1
対象ネットワーク: ao.TN.1
この仕様は、標準のaoトークンプロセスに必要なメッセージハンドラと機能を説明します。この標準の実装は通常、プロセスによって維持される希少性を持つ転送可能な資産を制御する能力をユーザーに提供します。
各準拠プロセスは、プロセスが表す資産の所有権を符号化するために、残高の台帳を実装することが期待されます。準拠プロセスには、通常、トークンの所有権の希少性を保証するための安全策を伴って、この台帳を変更するためのメソッドのセットがあります。
さらに、この仕様では「サブレッジャー」プロセスタイプを説明しています。これは、実装されると、親プロセスから子プロセスにトークンの数を分割移動する能力を提供します。この子プロセスは同じトークンインターフェース仕様を実装します。サブレッジャープロセスのFrom-Module
が参加者によって信頼される場合、これらのサブレッジャーを使って「ソース」トークンで取引することができます。これは、直接メッセージを交換することなく行えます。これにより、プロセスが混雑している場合でも、参加者はそのプロセスからトークンを使用できます。オプションとして、参加者がサブレッジャープロセスが実行されているModule
を信頼している場合、これらのプロセス間での残高を代替可能として扱うことができます。この結果、任意の数の並列プロセス、およびそれに伴う取引を単一のトークンで同時に処理できます。
トークンプロセス
仕様に準拠したトークンプロセスは、さまざまな形式のメッセージに応答し、各形式はAction
タグで指定されます。トークンがサポートしなければならないAction
メッセージの完全なセットは以下の通りです:
Name | Description | Read-Only |
---|---|---|
Balance | get the balance of an identifer | ✔️ |
Balances | get a list of all ledger/account balances | ✔️ |
Transfer | send 1 or more units from the callers balance to one or move targets with the option to notify targets | ❌ |
Mint | if the ledger process is the root and you would like to increase token supply | ❌ |
このセクションの残りでは、準拠トークンプロセスを生成するために必要なタグと、各Action
メッセージの形式およびその結果について説明します。
生成パラメータ
すべての準拠トークンプロセスは、その生成メッセージに以下の不変パラメータを含める必要があります:
Tag | Description | Optional? |
---|---|---|
Name | The title of the token, as it should be displayed to users. | ✔️ |
Ticker | A suggested shortened name for the token, such that it can be referenced quickly. | ✔️ |
Logo | An image that applications may deserire to show next to the token, in order to make it quickly visually identifiable. | ✔️ |
Denomination | The number of the token that should be treated as a single unit when quantities and balances are displayed to users. | ❌ |
メッセージングプロトコル
Balance(Target? : string)
ターゲットの残高を返します。ターゲットが指定されていない場合は、メッセージの送信者の残高を返す必要があります。
例 Action
メッセージ:
send({
Target = "{TokenProcess Identifier}",
Tags = {
Action = "Balance",
Target = "{IDENTIFIER}"
}
})
Example response message:
{
Tags = {
Balance = "50",
Target = "LcldyO8wwiGDzC3iXzGofdO8JdR4S1_2A6Qtz-o33-0",
Ticker = "FUN"
}
}
Balances()
トークンのすべての参加者の残高を返します。
send({
Target = "[TokenProcess Identifier]",
Tags = {
Action = "Balances",
Limit = 1000, # TODO: Is this necessary if the user is paying for the compute and response?
Cursor? = "BalanceIdentifer"
}
})
Example response message:
{
Data = {
"MV8B3MAKTsUOqyCzQ0Tsa2AR3TiWTBU1Dx0xM4MO-f4": 100,
"LcldyO8wwiGDzC3iXzGofdO8JdR4S1_2A6Qtz-o33-0": 50
}
}
Transfer(Target, Quantity)
送信者に十分な残高がある場合、Quantity
をTarget
に送信し、受取人にCredit-Notice
を発行し、送信者にDebit-Notice
を発行します。Credit-
およびDebit-Notice
は、元のTransfer
メッセージからすべてのタグをX-
プレフィックス付きで転送する必要があります。送信者に残高が不足している場合は、失敗し、送信者に通知します。
send({
Target = "[TokenProcess Identifier]",
Tags = {
{ name = "Action", value = "Transfer" },
{ name = "Recipient", value = "[ADDRESS]" },
{ name = "Quantity", value = "100" },
{ name = "X-[Forwarded Tag(s) Name]", value= "[VALUE]" }
}
})
成功した転送が行われた場合、Cast
が設定されていない場合は通知メッセージを送信する必要があります。
ao.send({
Target = "[Recipient Address]",
Tags = {
{ name = "Action", value = "Credit-Notice" },
{ name = "Sender", value = "[ADDRESS]" },
{ name = "Quantity", value = "100"},
{ name = "X-[Forwarded Tag(s) Name]", value= "[VALUE]" }
}
})
受取人は、メッセージのFrom-Process
タグから、どのトークンを受け取ったかを推測します。
Get-Info()
send({
Target = "{Token}",
Tags = {
Action = "Info"
}
})
Mint() [optional]
Mint
アクションを実装することで、プロセスは有効な参加者に新しいトークンを作成する方法を提供します。
send({
Target ="{Token Process}",
Tags = {
Action = "Mint",
Quantity = "1000"
}
})
サブレッジャープロセス
適切に機能するために、サブレッジャーはトークン契約の完全なメッセージングプロトコルを実装する必要があります(Mint
アクションを除く)。サブレッジャーはまた、プロセスのために追加の機能や生成パラメータを実装する必要があります。これらの変更は、次のセクションで説明されます。
生成パラメータ
すべての準拠したサブレッジャープロセスは、その生成メッセージに以下の不変パラメータを含める必要があります:
Tag | Description | Optional? |
---|---|---|
Source-Token | The ID of the top-most process that this subledger represents. | ❌ |
Parent-Token | The ID of the parent process that this subledger is attached to. | ❌ |
Credit-Notice
ハンドラ
Credit-Notice
メッセージを受信した際、準拠したサブレッジャープロセスは、対象のプロセスがParent-Token
であるかどうかを確認する必要があります。もしそうであれば、サブレッジャーはSender
の残高を指定された数量だけ増加させる必要があります。
Transfer(Target, Quantity)
トークンの受取人に渡されるCredit-Notice
メッセージに含まれる通常のタグに加えて、準拠したサブレッジャープロセスは、Source-Token
とParent-Token
の両方の値も提供する必要があります。これにより、Transfer
メッセージの受取人は、サブレッジャープロセスのModule
を信頼している場合、Source-Token
からの預金と同様の(代替可能な)受領をクレジットできるようになります。
修正されたCredit-Notice
は以下のように構成されるべきです:
ao.send({
Target = "[Recipient Address]",
Tags = {
{ name = "Action", value = "Credit-Notice" },
{ name = "Quantity", value = "100"},
{ name = "Source-Token", value = "[ADDRESS]" },
{ name = "Parent-Token", value = "[ADDRESS]" },
{ name = "X-[Forwarded Tag(s) Name]", value= "[VALUE]" }
}
})
Withdraw(Target?, Quantity)
すべてのサブレッジャーは、残高保有者がトークンを親台帳に引き出すことを許可する必要があります。Action: Withdraw
メッセージを受信した際、サブレッジャーはそのParent-Ledger
にAction
メッセージを送り、要求されたトークンを呼び出し元のアドレスに転送し、ローカルでアカウントから引き落とします。この転送により、呼び出し元に対してParent-Ledger
からCredit-Notice
が発行されます。
send({
Target = "[TokenProcess Identifier]",
Tags = {
{ name = "Action", value = "Withdraw" },
{ name = "Recipient", value = "[ADDRESS]" },
{ name = "Quantity", value = "100" }
}
})
Token Example
NOTE: When implementing a token it is important to remember that all Tags on a message MUST be "string"s. Using the
tostring
function you can convert simple types to strings.
if not balances then
balances = { [ao.id] = 100000000000000 }
end
if name ~= "Fun Coin" then
name = "Fun Coin"
end
if ticker ~= "Fun" then
ticker = "fun"
end
if denomination ~= 6 then
denomination = 6
end
-- handlers that handler incoming msg
handlers.add(
"transfer",
handlers.utils.hasMatchingTag("Action", "Transfer"),
function (msg)
assert(type(msg.Tags.Recipient) == 'string', 'Recipient is required!')
assert(type(msg.Tags.Quantity) == 'string', 'Quantity is required!')
if not balances[msg.From] then
balances[msg.From] = 0
end
if not balances[msg.Tags.Recipient] then
balances[msg.Tags.Recipient] = 0
end
local qty = tonumber(msg.Tags.Quantity)
assert(type(qty) == 'number', 'qty must be number')
-- handlers.utils.reply("Transfering qty")(msg)
if balances[msg.From] >= qty then
balances[msg.From] = balances[msg.From] - qty
balances[msg.Tags.Recipient] = balances[msg.Tags.Recipient] + qty
ao.send({
Target = msg.From,
Tags = {
Action = "Debit-Notice",
Quantity = tostring(qty)
}
})
ao.send({
Target = msg.Tags.Recipient,
Tags = {
Action = "Credit-Notice",
Quantity = tostring(qty)
}})
-- if msg.Tags.Cast and msg.Tags.Cast == "true" then
-- return
-- end
end
end
)
handlers.add(
"balance",
handlers.utils.hasMatchingTag("Action", "Balance"),
function (msg)
assert(type(msg.Tags.Target) == "string", "Target Tag is required!")
local bal = "0"
if balances[msg.Tags.Target] then
bal = tostring(balances[msg.Tags.Target])
end
ao.send({Target = msg.From, Tags = {
Target = msg.From,
Balance = bal,
Ticker = ticker or ""
}})
end
)
local json = require("json")
handlers.add(
"balances",
handlers.utils.hasMatchingTag("Action", "Balances"),
function (msg)
ao.send({
Target = msg.From,
Data = json.encode(balances)
})
end
)
handlers.add(
"info",
handlers.utils.hasMatchingTag("Action", "Info"),
function (msg)
ao.send({Target = msg.From, Tags = {
Name = name,
Ticker = ticker,
Denomination = tostring(denomination)
}})
end
)